package com.homestead.service.impl;

import com.homestead.result.RedisLockResult;
import com.homestead.service.RedisService;
import com.homestead.utils.LuaUtil;
import com.homestead.utils.MySupplier;
import com.homestead.utils.RStore;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author HanBin_Yang
 * @since 2021/5/23 22:42
 */
@Service
public class RedisServiceImpl implements RedisService {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 扣减redis库存操作
     * @param keyName key
     * @param amount 扣减数量
     * @param mode 扣减策列
     * @return 实际扣减数量(key不存在返回null)
     */
    @Override
    public Long decrStock(String keyName, long amount, RStore.DecrMode mode) {
        return redissonClient.getScript(LongCodec.INSTANCE).evalSha(RScript.Mode.READ_WRITE, LuaUtil.getSha("decrStock.lua"), RScript.ReturnType.INTEGER, Collections.singletonList(keyName), amount,  mode.getFlag());
    }

    @Override
    public Long decrStock(String keyName, long amount) {
        return decrStock(keyName, amount, RStore.DecrMode.DECR);
    }

    @Override
    public Long incrStock(String keyName, long amount) {
        return redissonClient.getScript(LongCodec.INSTANCE).evalSha(RScript.Mode.READ_WRITE, LuaUtil.getSha("incrStock.lua"), RScript.ReturnType.INTEGER, Collections.singletonList(keyName), amount);
    }

    @Override
    public void setStock(String keyName, long amount) {
        RAtomicLong atomicLong = redissonClient.getAtomicLong(keyName);
        atomicLong.set(amount);
    }

    /**
     *
     * @param keyName redis key
     * @param waitTime 等待时间 0不等待
     * @param expireTime redis过期时间
     * @param timeUnit 单位
     * @param supplier 业务代码提供者
     * @param <V> 返回类型
     * @return 返回
     * @throws Exception 异常
     */
    @Override
    public <V> RedisLockResult<V> executeTryLock(String keyName, int waitTime, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) throws Exception {
        RLock lock = redissonClient.getLock(keyName);
        // 上锁
        boolean lockFlag = lock.tryLock(waitTime, expireTime, timeUnit);

        if (!lockFlag) {
            return RedisLockResult.fail();
        }

        try {
            // 业务代码
            return RedisLockResult.success(supplier.get());
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("executeTryLock keyName解锁失败，lockName={}", keyName, e);
            }
        }
    }

    @Override
    public <V> RedisLockResult<V> executeTryLock(RLock lock, int waitTime, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) throws Exception {
        // 上锁
        boolean lockFlag = lock.tryLock(waitTime, expireTime, timeUnit);

        if (!lockFlag) {
            return RedisLockResult.fail();
        }

        try {
            // 业务代码
            return RedisLockResult.success(supplier.get());
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("executeTryLock RLock解锁失败，lockName={}", lock.getName(), e);
            }
        }
    }

    @Override
    public <V> V executeLock(String keyName, int expireTime, TimeUnit timeUnit, MySupplier<V> supplier) throws Exception {
        RLock lock = redissonClient.getLock(keyName);
        // 上锁
        lock.lock(expireTime, timeUnit);

        try {
            // 业务代码
            return supplier.get();
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                logger.error("executeLock keyName解锁失败，lockName={}", keyName, e);
            }
        }
    }
}
